package com.zengjianjun.oss.platform.aliyun;

import cn.hutool.core.io.FileUtil;
import com.alibaba.fastjson2.JSON;
import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.model.*;
import com.aliyuncs.exceptions.ClientException;
import com.zengjianjun.oss.enums.OssPlatformEnum;
import com.zengjianjun.oss.exception.OssException;
import com.zengjianjun.oss.platform.AbstractOssClient;
import com.zengjianjun.oss.platform.param.FileListParam;
import com.zengjianjun.oss.platform.result.FileListResult;
import com.zengjianjun.oss.properties.AliYunOssProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

import static com.aliyun.oss.common.utils.AuthUtils.*;

/**
 * @author zengjianjun
 */
@Slf4j
@Component
public class AliYunOssClient extends AbstractOssClient {

    private OSS ossClient;

    @Resource
    private AliYunOssProperties aliYunOssProperties;

    @PostConstruct
    public void init() {
        try {
            log.info("init aliyun oss start......");
            this.initSystemEnv();
            // 从环境变量中获取访问凭证。请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            ossClient = new OSSClientBuilder().build(aliYunOssProperties.getEndpoint(), credentialsProvider, this.getConfig());
            log.info("init aliyun oss success......");
        } catch (ClientException e) {
            log.error("init aliyun oss error：", e);
            throw OssException.build(e);
        }
    }

    /**
     * 获取配置信息
     *
     * @return ossClient 配置
     */
    private ClientBuilderConfiguration getConfig() {
        ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
        // 设置OSSClient允许打开的最大HTTP连接数，默认为1024个。
        conf.setMaxConnections(aliYunOssProperties.getMaxConnections());
        // 设置Socket层传输数据的超时时间，默认为50000毫秒。
        conf.setSocketTimeout(aliYunOssProperties.getSocketTimeout());
        // 设置建立连接的超时时间，默认为50000毫秒。
        conf.setConnectionTimeout(aliYunOssProperties.getConnectionTimeout());
        // 设置失败请求重试次数，默认为3次。
        conf.setMaxErrorRetry(aliYunOssProperties.getMaxErrorRetry());
        return conf;
    }

    @Override
    protected OssPlatformEnum get() {
        return OssPlatformEnum.aliyun;
    }

    @Override
    public List<FileListResult> getFileList(FileListParam param) {
        return this.getFilePageList(param, false);
    }

    @Override
    public FileListResult getFile(String bucketName, String fileKey) {
        // 获取全部元数据可以使用：ObjectMetadata metadata = ossClient.getObjectMetadata(bucketName, objectName);
        log.info("[{}] 获取文件原数据：bucketName = {}, fileKey = {}", get().name(), bucketName, fileKey);
        long startTime = System.currentTimeMillis();
        SimplifiedObjectMeta objectMeta = ossClient.getSimplifiedObjectMeta(bucketName, fileKey);
        long endTime = System.currentTimeMillis();
        log.info("[{}] 获取文件原数据：result = {}, 耗时{}ms", get().name(), JSON.toJSONString(objectMeta), endTime - startTime);
        return FileListResult.builder()
                .bucketName(bucketName)
                .size(objectMeta.getSize())
                .key(fileKey)
                .build();
    }

    @Override
    public List<FileListResult> getFullFileList(FileListParam param) {
        return this.getFilePageList(param, true);

    }

    /**
     * 查询文件分页列表
     *
     * @param param     查询参数
     * @param selectAll 是否查询所有
     * @return 文件信息列表
     */
    private List<FileListResult> getFilePageList(FileListParam param, boolean selectAll) {
        List<OSSObjectSummary> list = new ArrayList<>();
        String nextContinuationToken = null;
        ListObjectsV2Result result;
        AtomicInteger page = new AtomicInteger();
        // 最大单次数量为200
        int maxKeys = Math.min(param.getMaxKeys(), 200);
        do {
            long startTime = System.currentTimeMillis();
            log.info("[{}] 分页查询文件列表：currentPage= {}, param = {}", get().name(), page.getAndIncrement(), JSON.toJSONString(param));
            ListObjectsV2Request listObjectsV2Request = new ListObjectsV2Request(param.getBucketName())
                    .withMaxKeys(maxKeys).withPrefix(param.getPrefix()).withStartAfter(param.getStartAfter());
            // 分页列举，每次传入上次返回结果中的nextContinuationToken。
            listObjectsV2Request.setContinuationToken(nextContinuationToken);
            result = ossClient.listObjectsV2(listObjectsV2Request);
            List<OSSObjectSummary> objectSummaries = result.getObjectSummaries();
            list.addAll(objectSummaries);
            nextContinuationToken = result.getNextContinuationToken();
            long endTime = System.currentTimeMillis();
            log.info("[{}] 分页查询文件列表：totalCount = {}, 耗时{}ms", get().name(), objectSummaries.size(), endTime - startTime);
        } while (result.isTruncated() && selectAll);
        List<FileListResult> resultList = new ArrayList<>(list.size());
        list.forEach(data -> resultList.add(FileListResult.builder()
                .bucketName(data.getBucketName())
                .key(data.getKey())
                .size(data.getSize())
                .build()));
        return resultList;
    }

    @Override
    public void download(String bucketName, String fileKey, String filePath) {
        // 下载Object到本地文件，并保存到指定的本地路径中。如果指定的本地文件存在会覆盖，不存在则新建。
        // 如果未指定本地路径，则下载后的文件默认保存到示例程序所属项目对应本地路径中。
        log.info("[{}] 下载文件到本地: bucketName = {}, fileKey = {}, filePath = {}", get().name(), bucketName, fileKey, filePath);
        long startTime = System.currentTimeMillis();
        File file = new File(filePath);
        FileUtil.touch(file);
        ossClient.getObject(new GetObjectRequest(bucketName, fileKey), file);
        long endTime = System.currentTimeMillis();
        log.info("[{}] 下载文件到本地: 耗时{}ms", get().name(), endTime - startTime);
    }

    /**
     * 设置环境变量
     */
    private void initSystemEnv() {
        log.info("[{}] init system env start...", get().name());
        // 设置环境变量
        String ossAccessKeyId = StringUtils.trim(System.getenv(ACCESS_KEY_ENV_VAR));
        if (!Objects.equals(ossAccessKeyId, aliYunOssProperties.getOssAccessKeyId())) {
            this.setSystemEvn(ACCESS_KEY_ENV_VAR, aliYunOssProperties.getOssAccessKeyId());
            log.info("[{}] 环境变量：OSS_ACCESS_KEY_ID = {}", get().name(), StringUtils.trim(System.getenv(ACCESS_KEY_ENV_VAR)));
        }
        String ossAccessKeySecret = StringUtils.trim(System.getenv(SECRET_KEY_ENV_VAR));
        if (!Objects.equals(ossAccessKeySecret, aliYunOssProperties.getOssAccessKeySecret())) {
            this.setSystemEvn(SECRET_KEY_ENV_VAR, aliYunOssProperties.getOssAccessKeySecret());
            log.info("[{}] 环境变量：OSS_ACCESS_KEY_SECRET = {}", get().name(), StringUtils.trim(System.getenv(SECRET_KEY_ENV_VAR)));
        }
        String ossSessionToken = StringUtils.trim(System.getenv(SESSION_TOKEN_ENV_VAR));
        if (!Objects.equals(ossSessionToken, aliYunOssProperties.getOssSessionToken())) {
            this.setSystemEvn(SESSION_TOKEN_ENV_VAR, aliYunOssProperties.getOssSessionToken());
            log.info("[{}] 环境变量：OSS_SESSION_TOKEN = {}", get().name(), StringUtils.trim(System.getenv(SESSION_TOKEN_ENV_VAR)));
        }
        log.info("[{}] init system env end...", get().name());
    }

    /**
     * 通过命令设置系统环境变量
     * 注：若获取不到环境变量，windows可以重启idea,linux系统可以手动执行source /etc/profile命令,再启动服务
     *
     * @param key   变量名称
     * @param value 变量值
     */
    private void setSystemEvn(String key, String value) {
        try {
            if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) {
                return;
            }
            ProcessBuilder processBuilder;
            Process process;
            if (SystemUtils.IS_OS_LINUX) {
                String[] command = {"sh", "-c", "echo export " + key + "='" + value + "' >> /etc/profile"};
                // 执行Linux命令
                processBuilder = new ProcessBuilder(command);
                process = processBuilder.start();
                // 获取子进程的环境变量,将子进程的环境变量添加到Java程序的环境变量中
                this.putEnvAll(processBuilder.environment());
                this.printCommand(process.waitFor(), process.getErrorStream());
                // 使环境变量生效
                String[] source = {"sh", "-c", "source /etc/profile"};
                processBuilder = new ProcessBuilder(source);
                process = processBuilder.start();
                // 获取子进程的环境变量,将子进程的环境变量添加到Java程序的环境变量中
                this.putEnvAll(processBuilder.environment());
                this.printCommand(process.waitFor(), process.getErrorStream());
            } else if (SystemUtils.IS_OS_WINDOWS) {
                // 构建Windows命令
                String[] setx = {"cmd", "/c", "setx", key, value};
                // 执行Windows命令
                processBuilder = new ProcessBuilder(setx);
                process = processBuilder.start();
                // 获取子进程的环境变量,将子进程的环境变量添加到Java程序的环境变量中
                this.putEnvAll(processBuilder.environment());
                this.printCommand(process.waitFor(), process.getErrorStream());
            } else {
                throw OssException.build("不支持的系统");
            }
        } catch (Exception e) {
            log.error("[{}] 环境变量设置失败: ", get().name(), e);
        }
    }

    private void putEnvAll(Map<String, String> childEnvironment) {
        Map<String, String> getenv = System.getenv();
        if (getenv != null && childEnvironment != null) {
            // 将子进程的环境变量添加到Java程序的环境变量中
            getenv.putAll(childEnvironment);
        }
    }

    /**
     * 打印执行命令的信息
     *
     * @param code        执行命令的结果
     * @param errorStream 错误信息流
     * @throws IOException io异常
     */
    private void printCommand(int code, InputStream errorStream) throws IOException {
        int bytesRead;
        log.info("[{}] command execute code = {}", get().name(), code);
        StringBuilder errorMsg = new StringBuilder();
        byte[] buffer = new byte[4096];
        while ((bytesRead = errorStream.read(buffer)) != -1) {
            errorMsg.append(new String(buffer, 0, bytesRead, StandardCharsets.UTF_8));
        }
        log.info("[{}] command execute info: {}", get().name(), errorMsg);
    }
}
